﻿using System;
using PingInfo.Helpers;
using PingInfo.Model;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Brushes = System.Windows.Media.Brushes;
using Size = System.Windows.Size;
using Timer = System.Timers.Timer;

namespace PingInfo.windows.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        private readonly Stopwatch _stopwatch = new Stopwatch();
        private readonly Timer _timer = new Timer();
        private static CountdownEvent _failedCountdownEvent = new CountdownEvent(0);
        private static CountdownEvent _successCountdownEvent = new CountdownEvent(0);
        #region Binding Parameters
        public int PingTimes
        {
            get => _pingTimes;
            set => RaiseAndSetIfChanged(ref _pingTimes, value, nameof(PingTimes));
        }
        public string IpInputStr
        {
            get => _ipInputStr;
            set => RaiseAndSetIfChanged(ref _ipInputStr, value, nameof(IpInputStr));
        }
        public ObservableCollection<IpEntity> IpEntities
        {
            get => _ipEntities;
            set
            {
                RaiseAndSetIfChanged(ref _ipEntities, value, nameof(IpEntities));
                RaiseAndSetIfChanged(ref _btnPingEnabled, value.Count > 0, nameof(BtnPingEnabled));
                ProgressMax = IpEntities.Count;
            }
        }
        public bool Include1
        {
            get => _include1;
            set => RaiseAndSetIfChanged(ref _include1, value, nameof(Include1));
        }
        public bool Include255
        {
            get => _include255;
            set => RaiseAndSetIfChanged(ref _include255, value, nameof(Include255));
        }
        public string UsedTime
        {
            get => _usedTime;
            set => RaiseAndSetIfChanged(ref _usedTime, value, nameof(UsedTime));
        }
        public int ProgressMax
        {
            get => _progressMax;
            set => RaiseAndSetIfChanged(ref _progressMax, value, nameof(ProgressMax));
        }
        public int ProgressValue
        {
            get => _progressValue;
            set
            {
                RaiseAndSetIfChanged(ref _progressValue, value, nameof(ProgressValue));
                if (value == ProgressMax)
                {
                    if (!PingAgain)
                    {
                        BtnPingEnabled = true;
                        BtnAddEnabled = true;
                        BtnPingChecked = false;
                    }
                    _stopwatch.Stop();
                }
                var ts = _stopwatch.Elapsed;
                UsedTime = $"{ts.TotalSeconds:f3}";
            }
        }
        public bool BtnAddEnabled
        {
            get => _btnAddEnabled;
            set => RaiseAndSetIfChanged(ref _btnAddEnabled, value, nameof(BtnAddEnabled));
        }
        public bool BtnPingEnabled
        {
            get => _btnPingEnabled;
            set => RaiseAndSetIfChanged(ref _btnPingEnabled, value, nameof(BtnPingEnabled));
        }
        public bool BtnPingChecked
        {
            get => _btnPingChecked;
            set => RaiseAndSetIfChanged(ref _btnPingChecked, value, nameof(BtnPingChecked));
        }
        public int ThreadCount
        {
            get => _threadCount;
            set => RaiseAndSetIfChanged(ref _threadCount, value, nameof(ThreadCount));
        }


        public int EnableCount
        {
            get => _enableCount;
            set => RaiseAndSetIfChanged(ref _enableCount, value, nameof(EnableCount));
        }
        public int DisableCount
        {
            get => _disableCount;
            set => RaiseAndSetIfChanged(ref _disableCount, value, nameof(DisableCount));
        }


        public int Timeout
        {
            get => _timeout;
            set => RaiseAndSetIfChanged(ref _timeout, value, nameof(Timeout));
        }

        public double ElementWidth
        {
            get => _elementWidth;
            set => RaiseAndSetIfChanged(ref _elementWidth, value, nameof(ElementWidth));
        }

        public bool LogVisibility
        {
            get => _logVisibility;
            set => RaiseAndSetIfChanged(ref _logVisibility, value, nameof(LogVisibility));
        }
        public int PingSize
        {
            get => _pingSize;
            set
            {
                if (value < 0) value = 0;
                if (value > 65535) value = 65535;
                RaiseAndSetIfChanged(ref _pingSize, value, nameof(PingSize));
            }
        }
        public bool PingAgain
        {
            get => _pingAgain;
            set => RaiseAndSetIfChanged(ref _pingAgain, value, nameof(PingAgain));
        }

        public int PingAgainInterval
        {
            get => _pingAgainInterval;
            set => RaiseAndSetIfChanged(ref _pingAgainInterval, value, nameof(PingAgainInterval));
        }
        public int PingAgainTimes
        {
            get => _pingAgainTimes;
            set => RaiseAndSetIfChanged(ref _pingAgainTimes, value, nameof(PingAgainTimes));
        }
        public string Title
        {
            get => $"{Assembly.GetExecutingAssembly().GetName().Name} v{Assembly.GetExecutingAssembly().GetName().Version}";
        }

        public bool LogMode
        {
            get => _logMode;
            set => RaiseAndSetIfChanged(ref _logMode, value, nameof(LogMode));
        }
        /// <summary>
        ///     计算控件宽度
        /// </summary>
        private double _elementWidth = 70;
        /// <summary>
        ///     快速PING 模式时，PING 次数
        /// </summary>
        private int _pingTimes = 1;

        /// <summary>
        ///     ip with Cidr
        /// </summary>
        private string _ipInputStr;
        /// <summary>
        ///     IP 列表
        /// </summary>
        private ObservableCollection<IpEntity> _ipEntities = new ObservableCollection<IpEntity>();
        /// <summary>
        ///     进度条总进度
        /// </summary>
        private int _progressMax;
        /// <summary>
        ///     进度条当前进度
        /// </summary>
        private int _progressValue;
        /// <summary>
        ///     PING 延时
        /// </summary>
        private int _timeout = 1;
        /// <summary>
        ///     PING 耗时
        /// </summary>
        private string _usedTime = "0";
        /// <summary>
        ///     添加时包含1
        /// </summary>
        private bool _include1;
        /// <summary>
        ///     添加时包含 255
        /// </summary>
        private bool _include255;
        /// <summary>
        ///     添加按钮可用状态
        /// </summary>
        private bool _btnAddEnabled = true;
        /// <summary>
        ///     PING 按钮可用状态
        /// </summary>
        private bool _btnPingEnabled;
        /// <summary>
        ///     PING 按钮选中状态
        /// </summary>
        private bool _btnPingChecked;
        /// <summary>
        ///     PING 线程数
        /// </summary>
        private int _threadCount = 128;
        /// <summary>
        ///     可达数量
        /// </summary>
        private int _enableCount;
        /// <summary>
        ///     不可达数量
        /// </summary>
        private int _disableCount;
        /// <summary>
        ///     日志控件可视状态
        /// </summary>
        private bool _logVisibility;
        /// <summary>
        ///     Ping 包大小
        /// </summary>
        private int _pingSize = 32;
        /// <summary>
        ///     是否持续 PING
        /// </summary>
        private bool _pingAgain;
        /// <summary>
        ///     持续 Ping 间隔
        /// </summary>
        private int _pingAgainInterval = 30;
        /// <summary>
        ///     持续 PING 次数
        /// </summary>
        private int _pingAgainTimes;
        /// <summary>
        ///     日志模式
        /// </summary>
        private bool _logMode = false;
        #endregion

        #region Binding Commands
        public RelayCommand<object> PingCommand
        {
            get => _pingCommand ?? (_pingCommand = new RelayCommand<object>(PingAction));
            set => _pingCommand = value;
        }
        public RelayCommand<object> AddCommand
        {
            get => _addCommand ?? (_addCommand = new RelayCommand<object>(AddAction));
            set => _addCommand = value;
        }


        public RelayCommand<object> AboutCommand
        {
            get => _aboutCommand ?? (_aboutCommand = new RelayCommand<object>(AboutAction));
            set => _aboutCommand = value;
        }
        //public RelayCommand<object> ScreenShotCommand
        //{
        //    get => _screenShotCommand ?? (_screenShotCommand = new RelayCommand<object>(ScreenShotAction));
        //    set => _screenShotCommand = value;
        //}
        public RelayCommand<object> ExportCommand
        {
            get => _exportCommand ?? (_exportCommand = new RelayCommand<object>(ExportAction));
            set => _exportCommand = value;
        }



        private RelayCommand<object> _pingCommand;
        private RelayCommand<object> _addCommand;
        private RelayCommand<object> _aboutCommand;
        private RelayCommand<object> _screenShotCommand;
        private RelayCommand<object> _exportCommand;
        #endregion

        #region Methods
        /// <summary>
        /// 生成报告
        /// </summary>
        /// <param name="obj"></param>
        private void ExportAction(object obj)
        {
            using (var fs = new StreamWriter("PingInfo_report" + DateTime.Now.ToString("yyyyMMdd_HHmmff") + ".html"))
            {
                string header =
                    "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"><html><head><title>PingInfo Report - " +
                    IpInputStr +
                    "</title><style>body{text-align:center;}table,td,th{border:1pxsolid#ddd;text-align:left;}tr:nth-child(even){background-color: #f2f2f2;}tr:hover {background-color: #ddd;}table{border-collapse:collapse;width:50%;margin:auto;}</style></head><body><h3>PingInfo Report - " +
                    IpInputStr +
                    "</h3><h4>Created by using <a href=\"https://gitee.com/fonaix\" target=\"newwin\">PingInfo</a></h4><h4>report generated at: " +
                    DateTime.Now +
                    "</h4><table border=\"1\" cellpadding=\"5\"><tr bgcolor=\"E0E0E0\"><th>IP Address<th>log<th>avg";
                string body = string.Empty;
                foreach (var ipe in IpEntities)
                {
                    body += string.Format("<tr><td>{0}<td>{1}<td>{2}", ipe.Ip, ipe.Log, ipe.Delay == -1 ? "timeout" : ipe.Delay + "");
                }
                string footer = "</table></body></html>";
                fs.WriteLine(header + body + footer);
            }
            Process.Start(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
        }

        /// <summary>
        /// 软件截图
        /// </summary>
        /// <param name="obj"></param>
        //private void ScreenShotAction(object obj)
        //{
        //    try
        //    {
        //        var image = ScreenShotHelper.CaptureActiveWindow();
        //        using (var fs = new FileStream(
        //                   "PingInfo_screenshot" + DateTime.Now.ToString("yyyyMMdd_HHmmff") + ".png", FileMode.Create))
        //        {
        //            image.Save(fs, ImageFormat.Png);
        //        }
        //        Process.Start(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
        //    }
        //    catch { }
        //}
        private void AboutAction(object obj)
        {
            Process.Start(obj.ToString());
        }

        private void AddAction(object obj)
        {
            if (string.IsNullOrEmpty(IpInputStr))
                return;
            if (Regex.IsMatch(IpInputStr, @"[^\d\.\-/]+"))
            {
                return;
            }
            bool flag = false;
            string[] ipCidr;
            string ipStart = string.Empty;
            string ipEnd = string.Empty;
            if (Regex.IsMatch(IpInputStr, @"^(\d+\.){3}\d+/\d{1,2}$"))
            {
                flag = true;
                ipCidr = IpInputStr.Split('/');
                ipStart = ipCidr[0];
                int cidr = int.Parse(ipCidr[1]);
                if (cidr > 30 || cidr < 16) return;
                uint availableHosts = CalculateAvailableHosts(cidr);
                double lastBlockIp = double.Parse(ipStart.Split('.')[3]);
                uint broadcast = (uint)Math.Ceiling(lastBlockIp / availableHosts) * availableHosts;
                ipEnd = IntToIp(IpToInt(ipStart) - uint.Parse(ipStart.Split('.')[3]) + broadcast);
            }
            else if (Regex.IsMatch(IpInputStr, @"^(\d+\.){3}\d+-(\d+\.){3}\d+$"))
            {
                flag = true;
                ipCidr = IpInputStr.Split('-');
                ipStart = ipCidr[0];
                ipEnd = ipCidr[1];
            }
            else if (Regex.IsMatch(IpInputStr, @"^(\d+\.){3}\d+$"))
            {
                flag = true;
                ipStart = IpInputStr;
                ipEnd = IpInputStr;
            }
            if (flag)
            {
                IpEntities = GenerateIpList(ipStart, ipEnd, Include1, Include255);
                //PingAction(null);
            }
        }
        private void DoPing(object ipe)
        {
            if (ipe == null) return;
            Thread.CurrentThread.IsBackground = true;
            var pp = new PingWindowsApi();
            for (int i = 0; i < PingTimes; i++)
            {
                bool pingRequest = pp.Ping(IPAddress.Parse(((IpEntity)ipe).Ip), Timeout * 1000, PingSize);
                if (pingRequest)
                {
                    var t = pp.IcmpReply.RoundTripTime;
                    var ot = ((IpEntity)ipe).Delay;
                    if (ot < 0)
                    {
                        ((IpEntity)ipe).Delay = t;
                    }
                    else
                    {
                        ((IpEntity)ipe).Delay = (ot + t) / 2;
                    }
                     ((IpEntity)ipe).Log += string.Format("{0}#: {1}ms, ttl={2,3}{3}", i + 1, t, pp.IcmpReply.Options.Ttl, i < PingTimes - 1 ? "\n" : "");
                }
                else
                {
                    ((IpEntity)ipe).Delay = -1;
                    ((IpEntity)ipe).Log += string.Format("{0}#: time out{1}", i + 1, i < PingTimes - 1 ? "\n" : "");
                }
            }
            if (((IpEntity)ipe).Delay != -1) _successCountdownEvent.Signal();
            else _failedCountdownEvent.Signal();
            EnableCount = IpEntities.Count - _successCountdownEvent.CurrentCount;
            DisableCount = IpEntities.Count - _failedCountdownEvent.CurrentCount;
            ProgressValue = IpEntities.Count * 2 - _successCountdownEvent.CurrentCount - _failedCountdownEvent.CurrentCount;
        }

        //检查线程池的方法
        private void CheckThreadPool(object state, bool timeout)
        {
            ThreadPool.GetAvailableThreads(out int workerThreads, out int _);
            ThreadPool.GetMaxThreads(out int maxWordThreads, out int _);
            //当可用的线数与池程池最大的线程相等时表示线程池中所有的线程已经完成
            if (workerThreads != maxWordThreads)
                return;
            //当执行此方法后CheckThreadPool将不再执行
            _rhw.Unregister(null);
            //此处加入所有线程完成后的处理代码
            ProgressValue = ProgressMax;
            EnableCount = IpEntities.Count - _successCountdownEvent.CurrentCount;
            DisableCount = IpEntities.Count - _failedCountdownEvent.CurrentCount;
        }
        private RegisteredWaitHandle _rhw;
        private void PingAction(object obj)
        {
            if (obj != null)
                if (obj.ToString() == "f5")
                {
                    BtnPingChecked = !BtnPingChecked;
                }
            BtnAddEnabled = !BtnPingChecked;
            _timer.AutoReset = true;
            if (!BtnPingChecked)
            {
                _timer.Stop();
                return;
            }
            if (PingAgain)
            {
                PingAgainTimes = 0;
                _timer.Interval = PingAgainInterval * 1000;
                _timer.Elapsed += HandleTimer;
                _timer.Start();
            }
            else
            {
                HandleTimer(null, null);
            }
        }

        private void HandleTimer(object sender, ElapsedEventArgs e)
        {
            ProgressValue = 0;
            EnableCount = 0;
            DisableCount = 0;
            _stopwatch.Restart();
            ThreadPool.SetMinThreads(ThreadCount, ThreadCount);
            ThreadPool.SetMaxThreads(ThreadCount * 2, ThreadCount * 2);
            _successCountdownEvent.Reset(IpEntities.Count);
            _failedCountdownEvent.Reset(IpEntities.Count);
            _rhw = ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), CheckThreadPool, null, 100, false);
            Parallel.ForEach(IpEntities, (ipEntity, loopState, index) =>
            {
                ipEntity.Background = Brushes.Transparent;
                ipEntity.Log = string.Empty;
                ipEntity.Delay = -1;
                ThreadPool.QueueUserWorkItem(DoPing, ipEntity);
            });
            if (PingAgain)
            {
                PingAgainTimes++;
            }
            else
            {
                _timer.Stop();
            }
        }


        /// <summary>
        ///     Generate IP Collection
        /// </summary>
        /// <param name="ipStart">IP AAddress Starting</param>
        /// <param name="ipEnd">IP Address Ending</param>
        /// <param name="isInclude1">Include 1</param>
        /// <param name="isInclude255">Include 255</param>
        /// <returns>ObservableCollection&lt;IpEntity></returns>
        private ObservableCollection<IpEntity> GenerateIpList(string ipStart, string ipEnd, bool isInclude1, bool isInclude255)
        {
            var ipTemp = new ObservableCollection<IpEntity>();
            uint iStartIp = IpToInt(ipStart);
            //uint iEndIp = IpToInt(ipStart) + CalculateUsableHosts(cidr) - uint.Parse(ipStart.Split('.')[3]);
            uint iEndIp = IpToInt(ipEnd);
            if (iEndIp >= iStartIp)
            {
                for (uint ip = iStartIp; ip <= iEndIp; ip++)
                {
                    byte d = (byte)(ip & 0x000000FF);
                    if (d == 0 || d == 255 && !isInclude255 || d == 1 && !isInclude1)
                        continue;
                    var ipe = new IpEntity
                    {
                        Ip = IntToIp(ip)
                    };
                    var lbl = new System.Windows.Controls.Label
                    {
                        Content = ipe.Ip
                    };
                    lbl.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                    var s = lbl.DesiredSize;
                    ElementWidth = s.Width;
                    ipTemp.Add(ipe);
                }
            }
            return ipTemp;
        }
        /// <summary>
        ///     Ip Address String to Integer
        /// </summary>
        /// <param name="ipStr">Ip Address</param>
        /// <returns>Integer</returns>
        private static uint IpToInt(string ipStr)
        {
            string[] ip = ipStr.Split('.');
            uint ipCode = 0xFFFFFF00 | byte.Parse(ip[3]);
            ipCode = ipCode & 0xFFFF00FF | uint.Parse(ip[2]) << 0x8;
            ipCode = ipCode & 0xFF00FFFF | uint.Parse(ip[1]) << 0x10;
            ipCode = ipCode & 0x00FFFFFF | uint.Parse(ip[0]) << 0x18;
            return ipCode;
        }
        /// <summary>
        ///     Ip Address Integer to String
        /// </summary>
        /// <param name="ipCode">Ip Address Integer</param>
        /// <returns>String</returns>
        private static string IntToIp(uint ipCode)
        {
            byte a = (byte)((ipCode & 0xFF000000) >> 0x18);
            byte b = (byte)((ipCode & 0x00FF0000) >> 0x10);
            byte c = (byte)((ipCode & 0x0000FF00) >> 0x8);
            byte d = (byte)(ipCode & 0x000000FF);
            string ipStr = $"{a}.{b}.{c}.{d}";
            return ipStr;
        }
        /// <summary>
        ///     Calculate Available Ip Address
        /// </summary>
        /// <param name="cidr">Cidr</param>
        /// <returns></returns>
        private static uint CalculateAvailableHosts(int cidr)
        {
            return uint.Parse(Math.Pow(2, 32 - cidr).ToString(CultureInfo.CurrentCulture));
        }
        #endregion
    }
}